package com.mangatta.ec2.monitor;

import android.content.Context;
import android.util.Log;
import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.services.cloudwatch.AmazonCloudWatchClient;
import com.amazonaws.services.cloudwatch.model.Datapoint;
import com.amazonaws.services.cloudwatch.model.Dimension;
import com.amazonaws.services.cloudwatch.model.GetMetricStatisticsRequest;
import com.amazonaws.services.cloudwatch.model.GetMetricStatisticsResult;
import com.mangatta.cloud.AmazonCloudWatchFactory;
import com.mangatta.cloud.AwsDetail;
import com.mangatta.cloud.Statistic;
import com.mangatta.cloud.alarm.AlarmConfiguration;
import com.mangatta.cloud.dao.AlarmConfigDao;
import com.mangatta.cloud.dao.InstanceDetailDao;
import com.mangatta.cloud.dao.InstanceStatusDao;
import com.mangatta.cloud.ec2.Ec2Instance;
import com.mangatta.ec2.util.StringHelper;
import com.mangatta.ec2.util.Util;

import java.util.*;

public class Ec2Monitor {
    private static final String TAG = Ec2Monitor.class.getSimpleName();
    private AmazonCloudWatchClient amazonCloudWatchClient = null;
    private InstanceDetailDao instanceDao;
    private AlarmConfigDao alarmConfigDao;
    private InstanceStatusDao instanceStatusDao;
    private Context context;

    public Ec2Monitor(Context context) {
        this.context = context;
        this.instanceDao = new InstanceDetailDao(context);
        this.alarmConfigDao = new AlarmConfigDao(context);
        this.instanceStatusDao = new InstanceStatusDao(context);
        updateAWSCredentials();
    }

    public void updateAWSCredentials(){
    	AWSCredentials awsCredentials = Util.getAWSCredentails(context);
    	if(awsCredentials != null){
    		AmazonCloudWatchFactory cloudWatchFactory = new AmazonCloudWatchFactory(awsCredentials);
    		synchronized (Ec2Monitor.this) {
    			amazonCloudWatchClient = cloudWatchFactory.getAmazonCloudWatchClient();
    		}
    	}
    }
    
    public Map<Ec2Instance, String> checkMetricHealth() {
        Log.d(TAG, "Checking metrics health");
        if (amazonCloudWatchClient == null) {
            return null;
        }

        Map<Ec2Instance, String> unhealthyInstances = new HashMap<Ec2Instance, String>();
        List<Ec2Instance> instanceList = instanceDao.getInstances(false);
        for (Ec2Instance instance : instanceList) {
            if (instance != null) {
                List<String> alarmNames = getAlarmsForInstance(instance);
                if (alarmNames != null && alarmNames.size() > 0) {
                    unhealthyInstances.put(instance, StringHelper.join(alarmNames, ","));
                }
            }
        }
        return unhealthyInstances;
    }

    private List<String> getAlarmsForInstance(Ec2Instance instance) {
        String instanceId = instance.getInstanceId();
        List<String> alarmNames = new ArrayList<String>();
        List<AlarmConfiguration> alarmConfigs = alarmConfigDao.getAlarmConfigsForInstance(instanceId);
        for (AlarmConfiguration alarmConfig : alarmConfigs) {
            Datapoint metric = getAlarmingMetrics(instanceId, alarmConfig);
            if (metric != null) {
                alarmNames.add(alarmConfig.getDisplayName());
            }
        }
        return alarmNames;
    }

    private Datapoint getAlarmingMetrics(String instanceId, AlarmConfiguration alarmConfig) {
        List<Datapoint> metrics = getMetrics(instanceId, alarmConfig);
        if (metrics == null || metrics.isEmpty()) {
            return null;
        }

        for (Datapoint metric : metrics) {
            if (!Util.isEmpty(alarmConfig.getThreshold()) && alarmConfig.getThreshold().compareTo(metric.getMaximum()) >= 0) {
                return metric;
            }
        }
        return null;
    }

    private List<Datapoint> getMetrics(String instanceId, AlarmConfiguration alarmConfig) {
        Calendar startTime = Calendar.getInstance();

        int pullInterval = Util.getPullIntervalInSeconds(context);
        startTime.add(Calendar.SECOND, -1 * Math.max(pullInterval, 900));
        Date startDate = new Date();
        startDate.setTime(startTime.getTime().getTime());

        Calendar endTime = Calendar.getInstance();
        Date endDate = new Date();
        endDate.setTime(endTime.getTime().getTime());

        GetMetricStatisticsRequest metricStatisticsRequest = new GetMetricStatisticsRequest()
                .withNamespace(AwsDetail.AMAZON_EC2.getNamespace())
                .withMetricName(alarmConfig.getMetric().getName())
                .withUnit(alarmConfig.getUnit())
                .withStartTime(startDate)
                .withEndTime(endDate)
                .withStatistics(Statistic.getDefault().getName())
                .withPeriod(pullInterval)  //take poll interval as period.
                .withDimensions(new Dimension().withName("InstanceId").withValue(instanceId)); //filter by instance.

        try {
            GetMetricStatisticsResult metricStatisticsResult =
                    amazonCloudWatchClient.getMetricStatistics(metricStatisticsRequest);
            @SuppressWarnings("unchecked") List<Datapoint> dataPoints =
                    (List<Datapoint>) metricStatisticsResult.getDatapoints();
            try {
                instanceStatusDao.persistInstanceStatus(instanceId, alarmConfig, dataPoints);
            } catch (Exception e) {
                Log.e(TAG, "Exception occurred while persisting data in the database");
            }
            Log.i(TAG, String.format("Got data points of size %d", dataPoints == null ? 0 : dataPoints.size()));
            return dataPoints;
        } catch (AmazonServiceException ase) {
            if (ase.getStatusCode() == 403) {
                Log.e(TAG, "Invalid aws credentials", ase);
            } else {
                Log.e(TAG, "Amazon Service Exception", ase);
            }
        } catch (AmazonClientException ace) {
            Log.e(TAG, "Invalid input or Check your internet connection", ace);
        } catch (Exception e) {
            Log.e(TAG, "Exception while getting metrics", e);
        }
        return null;
    }
}
